#ifndef __CPU_H__
#define __CPU_H__

#define OUTPUTINSTRUCTIONS
#define ADDRESS_WIDTH 4

#include <fstream>
#include <map>

#include "Comparison.h"
#include "OpCodes.h"
#include "SmallWord.h"
#include "Word.h"

// the CPU
class CPU
{
	public:
		// The size of the memory
		static const unsigned int MEMORY_SIZE = 512;

		// number of index (I) registers
		static const unsigned int INDEX_REGISTER_COUNT = 6;

		// total number of registers (6 index registers plus Register A, X, J)
		static const unsigned int TOTAL_REGISTER_COUNT = CPU::INDEX_REGISTER_COUNT + 3;

		// constructor
		CPU(void);

		// destructor
		~CPU(void);

		// Gets a deep clone of this object
		CPU* clone() const;

		// Gets the comparison indicator
		Comparison comparison_indicator() const;

		// Dumps the core
		// os: output stream
		void dump_core(std::ostream* const os) const;

		// Executes the word
		// word: instruction to execute
		void execute(const Word* const instruction);

		// Gets the current instruction word
		const Word* const instruction_word() const;

		// Gets the address of the current instruction word
		unsigned int instruction_pointer() const;

		// Sets the address of the instruction pointer
		void instruction_pointer(const unsigned int address);

		// loads the contents of the file into memory
		// fs: file stream to load from
		void load_memory(std::ifstream* const fs);

		// Gets the word at the specified address
		// address: memory address
		Word* memory(const unsigned int address) const;

		// Gets the value of the overflow toggle
		bool overflow() const;

		// Stores the contents of the memory into a file
		// fs: file stream to store into
		void store_memory(std::ofstream* const fs);

		// gets the number of CPU ticks
		unsigned int Ticks();

	private:
		// The comparison indicator
		Comparison comparison;

		// mapping of opcodes to the names of the opcode
		std::map<OpCodes, std::pair<unsigned int, std::string*> > executionTimesAndNames;

		// Holds the address value of the current instruction pointer
		unsigned int instructionPointer;

		// the overflow toggle
		bool overflowToggle;

		// holds the memory
		Word** pMemory;

		// Register A (Action)
		Word registerA;

		// Register I[1-6] (Index)
		SmallWord registerI[CPU::INDEX_REGISTER_COUNT];

		// Register J (Jump)
		SmallWord registerJ;

		// holds the registers in the order that they are (de)serialized
		// all index registers and reg A, X, J
		BaseWord* registerSerializationSequence[CPU::TOTAL_REGISTER_COUNT];

		// Register X (Extended)
		Word registerX;

		// ticks elapsed since start
		unsigned int ticks;

		// executes the ADD or SUB Op
		void execute_add_subtract(const Word* const instruction);

		// creates a buffer that combines the A and X register
		unsigned char* combine_ax_registers() const;

		// Distributes the buffer over the A and X register
		// buffer: buffer of size get_size(registerA) + get_size(registerX)
		void distribute_buffer_over_ax(const unsigned char* const buffer);

		// Executes DIV
		// instruction: division instruction
		void execute_divide(const Word* const instruction);

		// Enter value in register
		void execute_enter_register(BaseWord* const targetRegister, const Word* const instruction, bool negative);

		// Handles CMP[1-6]
		void handle_compare_index_register(const SmallWord* const targetRegister, const Word* const instruction);

		// Handles CMP[A|X]
		void handle_compare_register(const Word* const targetRegister, const Word* const instruction);

		// Handles INC?_DEC?_ENT?_ENN?
		void handle_inc_dec_ent_enn(BaseWord* const targetRegister, const Word* const instruction);

		// Executes JL, JE, JG, JGE, JNE, JLE
		// instruction: division instruction
		void handle_jump(const Word* const instruction);

		// Executes J?N, J?Z, J?P, J?NN, J?NZ, J?NP
		// instruction: current instruction word
		// targetRegister: the target register
		void handle_jump_on_register_status(const Word* const instruction, const BaseWord* const targetRegister);

		// Gets the indexed address value
		unsigned int indexed_address_value(const Word* const instruction);

		// initializes the object
		void initialize();

		// Executes JMP
		// Instruction: current instruction word
		void jump(const Word* const instruction);

		// Executes a jump if the Comparison Indicator is exactly the same as the condition
		// condition: exact condition
		// Instruction: current instruction word
		void jump_on_exact(Comparison condition, const Word* const instruction);

		// Executes a jump is the comparison indicator is set to either first or second
		// first: first potential condition
		// second: second potential condition
		// Instruction: current instruction word
		void jump_on_any_condition(Comparison first, Comparison second, const Word* const instruction);

		// Executes JOV
		// Instruction: current instruction word
		void jump_on_overflow(const Word* const instruction);

		// Executes JNOV
		// Instruction: current instruction word
		void jump_on_no_overflow(const Word* const instruction);

		// Executes JSJ
		// Instruction: current instruction word
		void jump_save_jump(const Word* const instruction);

		// loads content into a register
		void load_register(BaseWord* const targetRegister, const Word* const instruction, bool negative);

		// Executes MOVE
		// instruction: move instruction
		void move(const Word* const instruction);

		// Executes MUL
		// instruction: multiplication instruction
		void multiply(const Word* const instruction);

		// populates the OpCodeNames map
		void populate_execution_times();

		// sets the comparison indicator
		void set_comparison_indicator(signed int registerValue, signed int otherValue);

		// Shift the values in the buffer left
		// buffer: buffer to shift
		// bufferSize: buffer size
		// shiftAmount: amount to shift the buffer
		// isCyclical: indicator of whether we are doing a cyclical shift or not
		void shift_buffer_left(unsigned char* buffer, int bufferSize, int shiftAmount, bool isCyclical);

		// Shift the values in the buffer right
		// buffer: buffer to shift
		// bufferSize: buffer size
		// shiftAmount: amount to shift the buffer
		// isCyclical: indicator of whether we are doing a cyclical shift or not
		void shift_buffer_right(unsigned char* buffer, int bufferSize, int shiftAmount, bool isCyclical);

		// Shifts A left according to the value of M
		// instruction: current instruction word
		void shift_left_a(const Word* const instruction);

		// Shifts A and X left as a single value according to the value of M
		// instruction: current instruction word
		// isCyclical: indicator of whether we are doing a cyclical shift or not
		void shift_left_aX(const Word* const instruction, bool isCyclical);

		// Shifts A right according to the value of M
		// instruction: current instruction word
		void shift_right_a(const Word* const instruction);

		// Shifts A and X left as a single value according to the value of M
		// instruction: current instruction word
		// isCyclical: indicator of whether we are doing a cyclical shift or not
		void shift_right_aX(const Word* const instruction, bool isCyclical);

		// stores content from a register into memory
		void store_index_register(const SmallWord* const targetRegister, const Word* const instruction);

		// stores content from a register into memory
		void store_register(const Word* const targetRegister, const Word* const instruction);

		// executes the STZ op
		void store_zero(const Word* const instruction);

		// updates the address with the index value
		void update_address_with_index(unsigned short &address, const Word* const instruction);

	#ifdef OUTPUTINSTRUCTIONS

		// Classes that use write_instruction
		friend class ExecutionContext;
		friend class Shell;

		// Outputs the address, index and field spec part as MIXAL
		void write_instruction(const Word* const instruction, const int address = -1);
	#endif
};
#endif
